﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "GbmpSetupWorkPlaneAction.h"
#include "GbmpSetupWorkPlaneByPickAction.h"
#include "IApplication.h"
#include "IMainWindow.h"
#include "IUiView.h"
#include "IReferencePlane.h"
#include "IUserTransaction.h"
#include "IModelView.h"
#include "GcmpBuiltInCategoryUniIdentities.h"
#include "GbmpModelViewUtil.h"
#include "LevelUtils.h"
#include "IGridNew.h"
#include "IGenericElement.h"
#include "Vector3dUtils.h"
#include "GbmpBuiltInCategoryUniIdentitiesNew.h"
#include "GbmpSetupWorkPlaneDlg.h"
#include "GbmpSwitchViewDlg.h"
#include "IPlane.h"
#include "GbmpModelViewTypes.h"
#include "IDocument.h"
#include "IElementBasicInformation.h"
#include "GbmpSwitchViewActionRequest.h"
#include "GbmpDocUtils.h"

#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"



#define ELEVATION_PREFIX    GBMP_TR(L"标高: ")
#define REFERENCEPLANE_PREFIX    GBMP_TR(L"参考面: ")
#define GRID_PREFIX            GBMP_TR(L"轴网： ");

///////////////////////////////////////////////////////////////////////////////////////
namespace gcmp
{
    GbmpSetupWorkPlaneAction::GbmpSetupWorkPlaneAction()
        : m_isUserCancelled(false)
        , m_setWorkPlaneStatus(SWPS_STAND_BY)
    {
    }

    GbmpSetupWorkPlaneAction::~GbmpSetupWorkPlaneAction()
    {
    }

    void GbmpSetupWorkPlaneAction::InitAction(IUiView* pCurrentView)
    {
        DBG_WARN_AND_RETURN_VOID_UNLESS(pCurrentView != nullptr, L"pCurrentView为空",L"GDMPLab",L"2024-03-30");
        gcmp::IDocument* pDoc = GetDoc();
        DBG_WARN_AND_RETURN_VOID_UNLESS(pDoc != nullptr, L"pDoc为空",L"GDMPLab",L"2024-03-30");

        //初始化可选的工作平面。
        //找出所有的标高，所有的标高都可以选为工作平面
        std::vector<const ILevel*> levels = gcmp::LevelUtils::GetStoreyLevelsFromLowToHigh(pDoc);
        for (int i = 0; i < (int)levels.size(); ++i)
        {
            std::wstring wsName = levels[i]->GetName();
            {
                AssignWorkPln wp;
                wp.m_name = ELEVATION_PREFIX + wsName;
                wp.m_elevation = levels[i];
                m_arrAllCandidates.push_back(wp);
            }
        }

        //找出可选的参照平面集合，只有内置的参照平面才可以选为工作平面
        std::vector<IElement*> arrPln = gcmp::GbmpDocUtils::GetElementsByCategory(pDoc, BuiltInCategoryUniIdentities::BICU_REFERENCE_PLANE);
        FOR_EACH(item, arrPln)
        {
            IReferencePlane *curPlane = quick_cast<IReferencePlane>(item);
            std::wstring wsName = curPlane->GetBasicInformation()->GetName();
            if (curPlane->GetReferencePlaneType() == ReferencePlaneType::BuiltIn || curPlane->GetReferencePlaneType() == ReferencePlaneType::UserDefined)
            {
                AssignWorkPln wp;
                wp.m_name = REFERENCEPLANE_PREFIX + wsName;
                wp.m_refPlane = curPlane;
                m_arrAllCandidates.push_back(wp);
            }
        }

        //轴网
        std::vector<IElement*> arrGrid = gcmp::GbmpDocUtils::GetElementsByCategory(pDoc, BuiltInCategoryUniIdentities::BICU_NEW_GRID);
        FOR_EACH(item, arrGrid)
        {
            IGenericElement* pGenericElement = quick_cast<IGenericElement>(item);
            if (pGenericElement == nullptr)
            {
                continue;
            }

            IGridNew* pGrid = dynamic_cast<IGridNew*>(pGenericElement->GetExternalObject());
            if (pGrid == nullptr)
            {
                continue;
            }

            std::wstring gridName = pGrid->GetName();
            if (pGrid->IsHorizontalStraight())
            {
                AssignWorkPln wp;
                std::wstring namePrefix = GRID_PREFIX;
                wp.m_name = namePrefix + gridName;
                wp.m_grid = pGrid;
                m_arrAllCandidates.push_back(wp);
            }
        }

        IModelView* pModelView = pCurrentView->GetModelView();
        std::wstring curName = GbmpModelViewUtil::GetWorkPlaneName(pModelView);
        GbmpSetupWorkPlaneDlg::RunParam rp;

        //下面这段逻辑的理由？
        int idx = -1;
        FOR_EACH_INDEX(i, m_arrAllCandidates)
        {
            if (m_arrAllCandidates[i].m_name == curName)//内置参照平面的名字不可能和视图的名字一样
            {
                idx = i;
            }
            rp.m_arrViewName.push_back(m_arrAllCandidates[i].m_name);
        }
        rp.m_idxCurView = idx;
        //正确的逻辑是根据视图，切换可选的参照平面名称。
        //但是，目前默认视图的工作平面和内置的参照平面并没有关系
        //如果要根据视图切换可选的参照平面，应该将视图的默认工作平面设置为内置的参照平面

        QWidget* pMainWindow = dynamic_cast<QWidget*>(IMainWindow::GetMainWindow());
        DBG_WARN_UNLESS(pMainWindow, L"pMainWindow为空",L"GDMPLab",L"2024-03-30");

        GbmpSetupWorkPlaneDlg dlg(rp, pMainWindow);
        int rt = dlg.exec();
        if (rt == QDialog::Accepted)
        {
            switch (rp.m_rt)
            {
            case GbmpSetupWorkPlaneDlg::RunParam::RT_BY_NAME:
            {
                int idx = rp.m_idxCurView;
                DBG_WARN_AND_RETURN_VOID_UNLESS(idx >= 0 && idx < (int)rp.m_arrViewName.size(), L"idx",L"GDMPLab",L"2024-03-30");
                setWorkPlaneBySelectIndex(pCurrentView, idx);
                MarkFinishStatus(ActionFinishStatus::Successful);
                return;
            }
            case GbmpSetupWorkPlaneDlg::RunParam::RT_PICKFACE:
            {
                m_setWorkPlaneStatus = SWPS_PICK_FACE;
                m_isUserCancelled = false;
                StartChildAction(NEW_AS_OWNER_PTR(GbmpSetupWorkPlaneByPickAction));
                MarkFinishStatus(ActionFinishStatus::Successful);
                return;
            }
            default:
                break;
            }
        }
        MarkFinishStatus(ActionFinishStatus::Successful);
    }

    void GbmpSetupWorkPlaneAction::SetWorkPlaneByReferencePlane(gcmp::IModelView* pModelView, const gcmp::IReferencePlane* pReferencePlane)
    {
        DBG_WARN_AND_RETURN_VOID_UNLESS(pModelView != nullptr, L"pModelView为空",L"GDMPLab",L"2024-03-30");
        IDocument* pDocument = GetDoc();
        DBG_WARN_AND_RETURN_VOID_UNLESS(pDocument, L"document为空",L"GDMPLab",L"2024-03-30");

        std::wstring viewType = View3DViewType;
        if (viewType == pModelView->GetUserDataId())

        {
            GbmpModelViewUtil::SetReferencePlaneIdOfWorkPlane(pModelView, pReferencePlane->GetBasicInformation()->GetElementId());
        }
        else
        {
            ElementId viewIdToSwitch = GetViewToSwitch(pModelView, pReferencePlane->GetGeometryPlane()->GetNormal());
            if (viewIdToSwitch.IsValid() == false)
            {
                return;
            }

            // 视图发生变化才切换视图
            if (viewIdToSwitch != pModelView->GetElementId())    
            {
                PostRequest(NEW_AS_OWNER_PTR(GbmpSwitchViewActionRequest, viewIdToSwitch));
            }

            gcmp::IModelView* pSwitchModelView = dynamic_cast<gcmp::IModelView*>(pDocument->GetElement(viewIdToSwitch));
            DBG_WARN_AND_RETURN_VOID_UNLESS(pSwitchModelView, L"pSwitchModelView无效",L"GDMPLab",L"2024-03-30");
            GbmpModelViewUtil::SetReferencePlaneIdOfWorkPlane(pSwitchModelView, pReferencePlane->GetBasicInformation()->GetElementId());
        }
    }

    void GbmpSetupWorkPlaneAction::GetViewCandidatesToSwitch(gcmp::IModelView* pModelView, const gcmp::Vector3d& planeNormal, std::vector<const IModelView*>& arrView)
    {
        DBG_WARN_AND_RETURN_VOID_UNLESS(pModelView != nullptr, L"pModelView为空",L"GDMPLab",L"2024-03-30");

        double angle_EPS = gcmp::Constants::MATH_PI_6;
        Vector3d workPlaneNormal = pModelView->GetWorkPlane()->GetNormal();
        double angle = gcmp::Vector3dUtils::GetAngle(workPlaneNormal, planeNormal);
        if(fabs(angle) > angle_EPS && fabs(angle) < (gcmp::Constants::MATH_PI-angle_EPS))//大于30度时需要切换，找出所有可以切换到的视图
        {
            std::vector<IElement*> allModelViews = gcmp::GbmpDocUtils::GetElementsByCategory(GetDoc(), BuiltInCategoryUniIdentities::BICU_MODEL_VIEW);
            FOR_EACH(pItem, allModelViews)
            {
                IModelView *view = quick_cast<IModelView>(pItem);
                gcmp::Vector3d workPlaneNormal = view->GetWorkPlane()->GetNormal();
                double dotValue = workPlaneNormal.Dot(planeNormal);
                if (fabs(dotValue) > angle_EPS)
                {
                    arrView.push_back(view);
                }
                else if (view->GetUserDataId() == View3DViewType)

                {
                    arrView.push_back(view);
                }
            }
        }
        else//如果不需要切换，将当前视图加进去
        {
            arrView.push_back(pModelView);
        }
    }

    void GbmpSetupWorkPlaneAction::setWorkPlaneBySelectIndex(IUiView* pCurrentView, int idx)
    {
        DBG_WARN_AND_RETURN_VOID_UNLESS(pCurrentView != nullptr, L"pCurrentView为空",L"GDMPLab",L"2024-03-30");
        DBG_WARN_AND_RETURN_VOID_UNLESS(idx >= 0, L"pCurrentView为空",L"GDMPLab",L"2024-03-30");
        AssignWorkPln& data = m_arrAllCandidates[idx];
        IDocument* pDocument = GetDoc();
        DBG_WARN_AND_RETURN_VOID_UNLESS(pDocument, L"document为空",L"GDMPLab",L"2024-03-30");

        // 得到选中的参照平面
        if (data.m_refPlane)
        {
            OwnerPtr<IUserTransaction> opUt = IUserTransaction::Create(pDocument, GBMP_TR(L"修改工作平面"));
            DBG_WARN_AND_RETURN_VOID_UNLESS(opUt.get(), L"opUt为空",L"GDMPLab",L"2024-03-30");
            SetWorkPlaneByReferencePlane(pCurrentView->GetModelView(), data.m_refPlane);
            opUt->Commit();
            return;
        }

        gcmp::Vector3d normal;
        gcmp::Coordinate3d coord;
        if(data.m_elevation) //标高
        {
            const IPlane* pPlane = data.m_elevation->GetReferencePlane();
            DBG_WARN_AND_RETURN_VOID_UNLESS(pPlane, L"gPlane为空",L"GDMPLab",L"2024-03-30");

            normal = pPlane->GetNormal();
            coord = pPlane->GetCoordinate();
        }
        else if(data.m_grid) // 轴网
        {
            std::vector<OwnerPtr<ISurface>> surfaces = data.m_grid->GetGridSurfaces();
            if (surfaces.size() != 1)
            {
                return;
            }

            IPlane* pPlane = dynamic_cast<IPlane*>(surfaces[0].get());
            if (pPlane == nullptr)
            {
                return;
            }
            
            normal = pPlane->GetNormal();
            coord = pPlane->GetCoordinate();
        }
        else
        {
            return;
        }

        //检查是否需要切换视图
        ElementId viewIdToSwitch = GetViewToSwitch(pCurrentView->GetModelView(), normal);
        if (viewIdToSwitch.IsValid() == false)
        {
            return;
        }

        PostRequest(NEW_AS_OWNER_PTR(GbmpSwitchViewActionRequest, viewIdToSwitch));
        SetWorkPlaneByModelViewIdAndCoordnate(viewIdToSwitch, coord, idx);
    }

    gcmp::ElementId GbmpSetupWorkPlaneAction::GetViewToSwitch(gcmp::IModelView* pModelView, const gcmp::Vector3d& planeNormal)
    {
        std::vector<const IModelView*> pViews;
        GetViewCandidatesToSwitch(pModelView, planeNormal, pViews);

        ElementId curModelViewId = ElementId::InvalidID;
        if (pViews.size() == 1)
        {
            curModelViewId = pViews[0]->GetBasicInformation()->GetElementId();
        }
        else if (pViews.size() > 1)
        {
            GbmpSwitchViewDlg switchViewDlg(pViews, nullptr);
            if (switchViewDlg.exec() == QDialog::Accepted)
            {
                curModelViewId = switchViewDlg.GetSelectedViewId();
            }
            else
            {
                return curModelViewId;
            }
        }
        else
        {
            std::wstring viewType = View3DViewType;
            IModelView* currentView = GbmpModelViewUtil::FindViewByName(GetDoc(), viewType, GBMP_TR(L"三维"));
            curModelViewId = currentView->GetBasicInformation()->GetElementId();
        }

        return curModelViewId;
    }

    void GbmpSetupWorkPlaneAction::SetWorkPlaneByModelViewIdAndCoordnate(const gcmp::ElementId& modelViewId, const gcmp::Coordinate3d& coord, int idx)
    {
        gcmp::IDocument* pDoc = GetDoc();
        DBG_WARN_AND_RETURN_VOID_UNLESS(pDoc, L"doc为空",L"GDMPLab",L"2024-03-30");
        PostRequest(NEW_AS_OWNER_PTR(GbmpSwitchViewActionRequest, modelViewId));

        IElement * element =  pDoc->GetElement(modelViewId);
        DBG_WARN_AND_RETURN_VOID_UNLESS(element != nullptr, L"modelView为空",L"GDMPLab",L"2024-03-30");
        if (IModelView * pModelView = dynamic_cast<IModelView*>(element))
        {
            OwnerPtr<IUserTransaction> opUt = IUserTransaction::Create(pDoc, GBMP_TR(L"修改工作平面"));
            DBG_WARN_AND_RETURN_VOID_UNLESS(opUt.get() != nullptr, L"opUt为空",L"GDMPLab",L"2024-03-30");
            {
                OwnerPtr<IPlane> gpln = IPlane::Create(coord.GetOrigin(), coord.GetX(), coord.GetY());
                pModelView->SetWorkPlane(gpln.get());

                AssignWorkPln& data = m_arrAllCandidates[idx];
                GbmpModelViewUtil::SetWorkPlaneName(pModelView, data.m_name);

                if (data.m_elevation)
                {
                    ElementId workLevelId = data.m_elevation->GetOwnerElement()->GetElementId();
                    GbmpModelViewUtil::SetWorkLevelId(pModelView, workLevelId);
                }
            }

            opUt->Commit();
        }
    }
}
